package com.loongtech.bi.support.chart;

import com.loongtech.bi.action.UIHelper;
import org.apache.commons.lang3.StringUtils;
import org.primefaces.json.JSONArray;
import org.primefaces.json.JSONException;
import org.primefaces.json.JSONObject;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Map.Entry;

/**
 * chart工具类
 * @author Marin
 *
 */
public class ChartTool {
	private List<?> resList;  // 前端查询结果，传到这个类里，被组装成可被highchart显示的json字符串
	
	private LinkedHashMap<String, String> allItmes;	// 所有被标注为显示的列（之前的系统可选择某列，动态刷新图表。现在暂时去掉这个功能，但这个字段保留）
	private String selectItem;					// 当前选择的显示列（默认为第一个被标注为ChartSelect的字段）
	private String ignoreSelectItem;

	private LinkedHashMap<String, Integer> selectItemToKey;   // 前端选中的字段名称和ChartData注解的key字段的对应关系
	private LinkedHashMap<Integer, List<String>> keyToChartData;  // ChartData注解的key字段与被标注为key的字段的对应关系，list的size大于1时，有多个指标线
	private LinkedHashMap<Integer, String> keyToChartDataByOneFiled;  //  有的时候可能一个指标要根据别的字段区分为多条线，由ChartData字段的multi字段决定
	private LinkedHashMap<String, String> chartDataToName;  // ChartData注解的字段名与name属性的对应关系 
	private LinkedHashMap<String, String> chartDataToNamePrefix;  // ChartData注解的字段名与name属性的对应关系 
	private String overflowGetter;	
	private String overflowName;


    public ChartTool(List<?> resList, Class<?> clazz, String selectItem, String ignoreSelectItem) {
        this.resList = resList;
        this.selectItem = selectItem;
        this.ignoreSelectItem = ignoreSelectItem;

        selectItemInit(clazz);
    }

	public ChartTool(List<?> resList, Class<?> clazz, String selectItem) {
		this.resList = resList;
		this.selectItem = selectItem;

		selectItemInit(clazz);
	}

	/**
	 * 生成图表数据
	 * @param chartType
	 * @return
	 */
	public String loadData(String chartType, boolean ...isPercent) {
		if(resList == null || resList.size() <= 0 || resList.get(0) == null) {
			System.out.println("查询结果为空，无图表显示");
			return null;
		}
		
		int size = resList.size();
		ChartModel cm = new ChartModel();
		String type = "";
		
		List<String> getters = keyToChartData.get(selectItemToKey.get(selectItem));
		// 数据和列

        getters.remove(ignoreSelectItem);

		int zhibiaoSize = getters.size();
		boolean multiByFiled = false;
		List<String> zhiBiaoList = new ArrayList<>();
		List<String> hengZuoBiaoList = new ArrayList<>();
		try {
			if (getters.size() == 1 && keyToChartDataByOneFiled.get(selectItemToKey.get(selectItem)) != null) {  // 由某个字段决定指标数的情况，key必须唯一
				for (Object oo : resList) {
					String ooGetter = "get" + keyToChartDataByOneFiled.get(selectItemToKey.get(selectItem)).substring(0,1).toUpperCase() + keyToChartDataByOneFiled.get(selectItemToKey.get(selectItem)).substring(1);
					Method mRes= oo.getClass().getMethod(ooGetter);
					String nn = mRes.invoke(oo).toString();
					if (zhiBiaoList.indexOf(nn) == -1) {
						zhiBiaoList.add(nn);
					}
					
					Method overflow1 = oo.getClass().getMethod(overflowGetter);
					String hzb = overflow1.invoke(oo).toString();
					if (hengZuoBiaoList.indexOf(hzb) == -1) {
						hengZuoBiaoList.add(hzb);
					}
				}
				multiByFiled = true;
				zhibiaoSize = zhiBiaoList.size();
				size = hengZuoBiaoList.size();
			}
		} catch (Exception e) {
		}
		
		Object[][] data = new Object[zhibiaoSize][size];  // 二维，横=线数，纵=横坐标数
		String[] columnkeys = new String[size];
		String[] names = new String[zhibiaoSize];
		int j = size - 1;	//用于图表和报表显示顺序相反的问题
		
		try {
			for (Entry<String, String> e : allItmes.entrySet()) {
				if(e.getValue().endsWith(selectItem)){
					// 标题
					//cm.setRowkeys(new String[]{e.getKey()});
					// 拼接反射的get方法名字
					//String getter = "get" + selectItem.substring(0,1).toUpperCase() + selectItem.substring(1);
					
					for (int i = 0; i < resList.size(); i++) {
						// 反射的数据对象
						Object o = resList.get(i);
						
						// 反射横坐标
						Method overflow = o.getClass().getMethod(overflowGetter);
						
						for(int k = 0; k < (multiByFiled ? 1 : zhibiaoSize); k++) {
							String getter = null;
							Method mRes= null;
							if (multiByFiled) {
								getter = "get" + getters.get(0).substring(0,1).toUpperCase() + getters.get(0).substring(1);
								mRes= o.getClass().getMethod(getter);
								// 反射结果
								String tmpGetter = "get" + keyToChartDataByOneFiled.get(selectItemToKey.get(selectItem)).substring(0,1).toUpperCase() + keyToChartDataByOneFiled.get(selectItemToKey.get(selectItem)).substring(1);
								Method tmpMRes= o.getClass().getMethod(tmpGetter);
								int zhibiaoIndex = zhiBiaoList.indexOf(tmpMRes.invoke(o).toString());
								
								String hzb1 = overflow.invoke(o).toString();
								int hengZuoBiaoIndex = hengZuoBiaoList.indexOf(hzb1);
								
								columnkeys[hengZuoBiaoIndex] = hzb1;
								
								data[zhibiaoIndex][hengZuoBiaoIndex] = mRes.invoke(o) == null ? "0" : mRes.invoke(o);

                                if (chartDataToNamePrefix.get(getters.get(k)) == null) {
                                    names[zhibiaoIndex] = zhiBiaoList.get(zhibiaoIndex);
                                } else {
                                    String mm = chartDataToNamePrefix.get(getters.get(k));
                                    Method legendMRes= o.getClass().getMethod(mm);
                                    names[zhibiaoIndex] = legendMRes.invoke(o).toString() + "-" + zhiBiaoList.get(zhibiaoIndex);
                                }
							} else {
								columnkeys[j-i] = overflow.invoke(o).toString();
								
								getter = "get" + getters.get(k).substring(0,1).toUpperCase() + getters.get(k).substring(1);
								mRes= o.getClass().getMethod(getter);
								// 反射结果
								data[k][j-i] = mRes.invoke(o);
								
								if (chartDataToNamePrefix.get(getters.get(k)) == null) {
									names[k] = chartDataToName.get(getters.get(k));
								} else {
									String mm = chartDataToNamePrefix.get(getters.get(k));
									Method legendMRes= o.getClass().getMethod(mm);
									names[k] = legendMRes.invoke(o).toString() + chartDataToName.get(getters.get(k));
								}
							}
							
							// 第一次的时候，获取返回类型
							if(i == 0) {
								type = mRes.getReturnType().toString();
							}
						}
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("解析数据错误");
		}
		
		// 整理纵坐标相同的数据
		Map<String, List<Integer>> tmpColumnkeysMap = new LinkedHashMap<String, List<Integer>>();  // key=列标列表
		for (int i = 0; i < columnkeys.length; i++) {
			List<Integer> kl = tmpColumnkeysMap.get(columnkeys[i]);
			if (kl == null) {
				kl = new ArrayList<Integer>();
				tmpColumnkeysMap.put(columnkeys[i], kl);
			}
			kl.add(i);  // 记录列标
		}
		
		if (tmpColumnkeysMap.size() < size) {  // 有重复的横坐标
			String[] strArray = new String[tmpColumnkeysMap.size()];
			columnkeys = (String[]) tmpColumnkeysMap.keySet().toArray(strArray);  // 最终横坐标
			
			Object[][] realData = new Object[zhibiaoSize][tmpColumnkeysMap.size()];  // 最终数据
			for (int i = 0; i < realData.length; i++) {      
	            for (int k = 0; k < realData[i].length; k++) {   
	            	realData[i][k] = "0";                      
	            }  
	        }  
			int index = 0;
			for (Entry<String, List<Integer>> entry : tmpColumnkeysMap.entrySet()) {
				List<Integer> vl = entry.getValue();
				int i0  = vl.get(0);  // 每组中第一列
				for (int i = 0; i < zhibiaoSize; i++) {
					for (int k = 1; k < vl.size(); k++) {
						if(type.toLowerCase().contains("int")) {
							data[i][i0] = Integer.parseInt(data[i][i0].toString()) + Integer.parseInt(data[i][vl.get(k)].toString());
						} else if(type.toLowerCase().contains("long")) {
							data[i][i0] = Long.parseLong(data[i][i0].toString()) + Long.parseLong(data[i][vl.get(k)].toString());
						} else if(type.toLowerCase().contains("double")) {
							data[i][i0] = Double.parseDouble(data[i][i0].toString()) + Double.parseDouble(data[i][vl.get(k)].toString());
						}
					}
					
					realData[i][index] = data[i][i0];
				}
				
				index++;
			}
			
			data = new Object[zhibiaoSize][tmpColumnkeysMap.size()];  // 最终数据
			data = realData;
		}
		
		cm.setRowkeys(new String[]{UIHelper.createStr("[{0}]{1} - {2}", overflowName, columnkeys[0], columnkeys[columnkeys.length - 1])});  // 标题
		cm.setColumnkeys(columnkeys);
		cm.setData(data);
		cm.setNames(names);

		// 处理返回值类型
		if(type.toLowerCase().contains("int")) type = "int";
		else if(type.toLowerCase().contains("long")) type = "long";
		else if(type.toLowerCase().contains("double") || type.toLowerCase().contains("float")) type = "double";
		
		// 得到json
		if ("pie".equals(chartType)) {  // 饼状图特殊处理
			return pieLongToJSON(cm);
		} else {
		    if (isPercent != null && isPercent.length > 0 && isPercent[0] == true) {
                return dataToJSON(cm, type, true);
            } else {
                return dataToJSON(cm, type, false);
            }
		}
	}
	
	// 将chartmodel对象中的long型二维数组转为是个饼状图的json数据（饼状图较特殊）
	private String pieLongToJSON(ChartModel chartModel) {
		JSONObject dataToJSON = new JSONObject();
		try {
			JSONArray rowKeys = new JSONArray(Arrays.asList(chartModel.getRowkeys()));
			dataToJSON.put("rowkeys", rowKeys);
			dataToJSON.put("hctitle", chartModel.getNames()[0]);
			dataToJSON.put("type", "pie");
			dataToJSON.put("names", chartModel.getColumnkeys());
			long[] data = chartModel.getPieData();
			//long[] data = chartModel.getData()[0];
			//System.out.println(Arrays.toString(chartModel.getColumnkeys()));
			//System.out.println(Arrays.toString(data));
			JSONArray jsonArray = new JSONArray();
			//System.out.println(Arrays.toString(data));
			Object[][] tmp = new Object[data.length][2];
			for (int i = 0; i < data.length; i++) {
				tmp[i][0] = chartModel.getColumnkeys()[i];
				tmp[i][1] = data[i];
			}
			jsonArray.put(tmp);
			dataToJSON.put("data", jsonArray);
		} catch (JSONException e) {
			e.printStackTrace();
		}
		return dataToJSON.toString();
	}

	/**
	 * 将chartmodel对象中的二维数组转为是个其他折线图或者柱状图的json数据
	 * 
	 * @param chartModel
	 * @return
	 */
	private String dataToJSON(ChartModel chartModel, String type, boolean isPercent) {
		if(chartModel == null) return "";
		
		JSONObject dataToJSON = new JSONObject();
		try {
			JSONArray rowKeys = new JSONArray(Arrays.asList(chartModel.getRowkeys()));
			dataToJSON.put("rowkeys", rowKeys);
			JSONArray columnKeys = new JSONArray(Arrays.asList(chartModel.getColumnkeys()));
			dataToJSON.put("columnkeys", columnKeys);
			JSONArray names = new JSONArray(Arrays.asList(chartModel.getNames()));
			dataToJSON.put("names", names);
			dataToJSON.put("hctitle", chartModel.getXTitle());
			dataToJSON.put("yTitle", chartModel.getYTitle());
			dataToJSON.put("type", chartModel.getType());
			
			// Long数据
			if("long".equals(type)){
				long max = -1;
				long min = 100000000;
				Object[][] data = chartModel.getData();
				JSONArray jsonArray = new JSONArray();
				for (int i = 0; i < data.length; i++) {
					JSONArray array = new JSONArray();
					for (int j = 0; j < data[i].length; j++) {
						Long v = data[i][j] == null ? 0 : Long.parseLong(data[i][j].toString().split("\\.")[0]);
						array.put(v);
						if (v > max) max = v;
						if (v < min) min = v;
					}

					jsonArray.put(array);
				}
				dataToJSON.put("data", jsonArray);
				dataToJSON.put("maxData", max);
				dataToJSON.put("minData", min);
			}
			
			// 整形数据
			else if("int".equals(type)){
				int max = -1;
				int min = 10000;
				Object[][] data = chartModel.getData();
				JSONArray jsonArray = new JSONArray();
				for (int i = 0; i < data.length; i++) {
					JSONArray array = new JSONArray();
					for (int j = 0; j < data[i].length; j++) {
						Integer v = data[i][j] == null ? 0 : Integer.parseInt(data[i][j].toString().split("\\.")[0]);
						array.put(v);
						if (v > max) max = v;
						if (v < min) min = v;
					}

					jsonArray.put(array);
				}
				dataToJSON.put("data", jsonArray);
				dataToJSON.put("maxData", max);
				dataToJSON.put("minData", min);
			}
			
			// Double数据
			else if("double".equals(type)){
				double max = -1;
				double min = 10000;
				Object[][] data = chartModel.getData();
				JSONArray jsonArray = new JSONArray();
				for (int i = 0; i < data.length; i++) {
					JSONArray array = new JSONArray();
					for (int j = 0; j < data[i].length; j++) {
						Double v = data[i][j] == null ? 0 : Double.parseDouble(data[i][j].toString());
						array.put(v);
						if (v > max) max = v;
						if (v < min) min = v;
					}

					jsonArray.put(array);
				}
				dataToJSON.put("data", jsonArray);
				dataToJSON.put("maxData", max);
				dataToJSON.put("minData", min);
				if (max < 1) {
					dataToJSON.put("percent", 1);
				} else {
					dataToJSON.put("percent", 0);
				}
			}

			if (isPercent) {
                dataToJSON.put("percent", 1);
            }
		} catch (JSONException e) {
			e.printStackTrace();
		}
		//System.out.println(dataToJSON.toString());
		return dataToJSON.toString();
	}
	
	public static LinkedHashMap<String, String> getAllItems(Class<?> clazz) {
		LinkedHashMap<String, String> result = new LinkedHashMap<String, String>();
		try{
			// 挨个遍历属性
			for (Field field : clazz.getDeclaredFields()) {
				ChartSelect select = field.getAnnotation(ChartSelect.class);
				
				if(select != null) {
					result.put(select.name(), field.getName());
				}
			}
		} catch(Exception e) {
			e.printStackTrace();
		}
		
		return result;
	}
	
	// 通过反射和注解，拿到所有需要显示在图表里的属性
	private void selectItemInit(Class<?> clazz){
		allItmes = new LinkedHashMap<String, String>();
		selectItemToKey = new LinkedHashMap<String, Integer>();
		keyToChartData = new LinkedHashMap<Integer, List<String>>();
		keyToChartDataByOneFiled = new LinkedHashMap<Integer, String>();
		chartDataToName = new LinkedHashMap<String, String>();
		chartDataToNamePrefix = new LinkedHashMap<>();
		
		try{
			// 挨个遍历属性
			for (Field field : clazz.getDeclaredFields()) {
				ChartSelect select = field.getAnnotation(ChartSelect.class);
				
				ChartData1 cd1 = field.getAnnotation(ChartData1.class);
				ChartData2 cd2 = field.getAnnotation(ChartData2.class);
				ChartData3 cd3 = field.getAnnotation(ChartData3.class);
				
				if(select != null) {
					// 放入下拉列表
					allItmes.put(select.name(), field.getName());
					// 选择列与Key对应关系
					selectItemToKey.put(field.getName(), select.key());
					
					//TODO 默认最后一个被选中
					if (selectItem == null || "".equals(selectItem)) {
						selectItem = field.getName();
					}
					
					// key为1的属性，加入默认值
					/*if(select.key() == 1)  {
						selectItem = field.getName();
					}*/
				}
				if(cd1 != null) {
					List<String> tmp = keyToChartData.get(cd1.key()) == null ? new ArrayList<String>() : keyToChartData.get(cd1.key());
					tmp.add(field.getName());
					keyToChartData.put(cd1.key(), tmp);
					
					if (cd1.multiByFiled() != null && !"".equals(cd1.multiByFiled())) {
						keyToChartDataByOneFiled.put(cd1.key(), cd1.multiByFiled());
					}
					
					if (cd1.legendPrefix() != null && !"".equals(cd1.legendPrefix())) {
						String legendMethod = "get" + cd1.legendPrefix().substring(0,1).toUpperCase() + cd1.legendPrefix().substring(1);
						chartDataToNamePrefix.put(field.getName(), legendMethod);
					} 
						
					chartDataToName.put(field.getName(), cd1.name());
					
				}
				if(cd2 != null) {
					List<String> tmp = keyToChartData.get(cd2.key()) == null ? new ArrayList<String>() : keyToChartData.get(cd2.key());
					tmp.add(field.getName());
					keyToChartData.put(cd2.key(), tmp);
					
					if (cd2.multiByFiled() != null && !"".equals(cd2.multiByFiled())) {
						keyToChartDataByOneFiled.put(cd2.key(), cd2.multiByFiled());
					}
					
					if (cd2.legendPrefix() != null && !"".equals(cd2.legendPrefix())) {
						String legendMethod = "get" + cd2.legendPrefix().substring(0,1).toUpperCase() + cd1.legendPrefix().substring(1);
						chartDataToNamePrefix.put(field.getName(), legendMethod);
					} 
					
					chartDataToName.put(field.getName(), cd2.name());
				}
				if(cd3 != null) {
					List<String> tmp = keyToChartData.get(cd3.key()) == null ? new ArrayList<String>() : keyToChartData.get(cd3.key());
					tmp.add(field.getName());
					keyToChartData.put(cd3.key(), tmp);
					
					if (cd3.multiByFiled() != null && !"".equals(cd3.multiByFiled())) {
						keyToChartDataByOneFiled.put(cd3.key(), cd3.multiByFiled());
					}
					
					if (cd3.legendPrefix() != null && !"".equals(cd3.legendPrefix())) {
						String legendMethod = "get" + cd3.legendPrefix().substring(0,1).toUpperCase() + cd3.legendPrefix().substring(1);
						chartDataToNamePrefix.put(field.getName(), legendMethod);
					}
						
					chartDataToName.put(field.getName(), cd3.name());
				}
			}
			
			// 设置横坐标
			overflowGetter(clazz);
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

	// 获取到对象放到图表的横坐标字段
	private void overflowGetter(Class<?> clazz) {
		try{
			// 挨个遍历属性
			for (Field field : clazz.getDeclaredFields()) {
				ChartFlow select = field.getAnnotation(ChartFlow.class);
				if(select != null) {
					
					// 横坐标的getter方法
					String name = field.getName();
					overflowGetter = "get" + name.substring(0,1).toUpperCase() + name.substring(1);
					
					// 横坐标名字
					overflowName =select.name();
				}
			}
		
		} catch(Exception e) {
			e.printStackTrace();
			System.out.println("反射横坐标出错，请检查对象：" + clazz);
		} 
		
		if(StringUtils.isEmpty(overflowGetter)) {
			System.out.println("对象中没有设定横坐标，请检查对象：" + clazz);
		}
		
	}

	public List<?> getResList() {
		return resList;
	}

	public void setResList(List<?> resList) {
		this.resList = resList;
	}

	public LinkedHashMap<String, String> getAllItmes() {
		return allItmes;
	}

	public void setAllItmes(LinkedHashMap<String, String> allItmes) {
		this.allItmes = allItmes;
	}

	public String getSelectItem() {
		return selectItem;
	}

	public void setSelectItem(String selectItem) {
		this.selectItem = selectItem;
	}
	
	public LinkedHashMap<String, Integer> getSelectItemToKey() {
		return selectItemToKey;
	}

	public void setSelectItemToKey(LinkedHashMap<String, Integer> selectItemToKey) {
		this.selectItemToKey = selectItemToKey;
	}

	public LinkedHashMap<Integer, List<String>> getKeyToChartData() {
		return keyToChartData;
	}

	public void setKeyToChartData(
			LinkedHashMap<Integer, List<String>> keyToChartData) {
		this.keyToChartData = keyToChartData;
	}

	public LinkedHashMap<String, String> getChartDataToName() {
		return chartDataToName;
	}

	public void setChartDataToName(LinkedHashMap<String, String> chartDataToName) {
		this.chartDataToName = chartDataToName;
	}
	
	public String getOverflowGetter() {
		return overflowGetter;
	}

	public void setOverflowGetter(String overflowGetter) {
		this.overflowGetter = overflowGetter;
	}

	public String getOverflowName() {
		return overflowName;
	}

	public void setOverflowName(String overflowName) {
		this.overflowName = overflowName;
	}

	public LinkedHashMap<Integer, String> getKeyToChartDataByOneFiled() {
		return keyToChartDataByOneFiled;
	}

	public void setKeyToChartDataByOneFiled(LinkedHashMap<Integer, String> keyToChartDataByOneFiled) {
		this.keyToChartDataByOneFiled = keyToChartDataByOneFiled;
	}

	public LinkedHashMap<String, String> getChartDataToNamePrefix() {
		return chartDataToNamePrefix;
	}

	public void setChartDataToNamePrefix(LinkedHashMap<String, String> chartDataToNamePrefix) {
		this.chartDataToNamePrefix = chartDataToNamePrefix;
	}
}
